#include "audio.h"
#include "wav_reader.h"
#include "mp3_reader.h"
#include "../base/basic_types.h"

#include <AL/al.h>
#include <AL/alc.h>
#include <stdio.h>
#include <string>
#include <map>
#include <vector>
#define MAX_SOUND_EFFECT_MIX 25
#define MAX_SOUND_STREAM_MIX 4
#define NUM_STREAM_BLOCKS 12
#define STREAM_BLOCK_SIZE 4096



namespace KAudio {



struct SoundBuffer 
{
	ALuint bufferId;
	unsigned sampleCnt;
};

struct SoundSource
{
	ALuint sourceId;
	int currentBuffer;
};

static ALCdevice* s_device = NULL;
static ALCcontext* s_context = NULL;
static std::map<std::string, int> s_bufferMap;
static std::vector<SoundBuffer> s_soundBuffer;
static std::vector<SoundSource> s_soundSource;

static std::vector<KMp3Decoder*> s_mp3Streams;
static ALuint s_streamSource;
static ALuint s_streamBuffers[NUM_STREAM_BLOCKS];
static std::vector<BYTE> s_streamDecodingBuffer(STREAM_BLOCK_SIZE);


bool Initialize()
{
	s_device = alcOpenDevice(NULL);
	if (!s_device)
		return false;

	s_context = alcCreateContext(s_device, NULL);
	if (!s_context)
		return false;
	
	alcMakeContextCurrent(s_context);

	// Reserve buffer index(-1) for special purpose
	SoundBuffer dummyBuffer;
	dummyBuffer.bufferId = -1;
	s_soundBuffer.push_back(dummyBuffer);

	s_soundSource.resize(MAX_SOUND_EFFECT_MIX);
	for (int i = 0; i < MAX_SOUND_EFFECT_MIX; ++i) {
		ALuint source;
		alGenSources(1, &source);

		alSourcef(source, AL_PITCH, 1);
		alSourcef(source, AL_GAIN, 1);
		alSource3f(source, AL_POSITION, 0, 0, 0);
		alSource3f(source, AL_VELOCITY, 0, 0, 0);
		alSourcei(source, AL_LOOPING, AL_FALSE);
		s_soundSource[i].sourceId = source;
		s_soundSource[i].currentBuffer = -1;
	}

	s_mp3Streams.resize(MAX_SOUND_STREAM_MIX);
	for (int i = 0; i < MAX_SOUND_STREAM_MIX; ++i)
		s_mp3Streams[i] = NULL;

	alGenSources(1, &s_streamSource);
	{
		std::vector<BYTE> zeroData(STREAM_BLOCK_SIZE);
		memset(&zeroData[0], 0, STREAM_BLOCK_SIZE);
		for (int i = 0; i < NUM_STREAM_BLOCKS; ++i) {
			alGenBuffers(1, &s_streamBuffers[i]);
			alBufferData(s_streamBuffers[i], AL_FORMAT_STEREO16, &zeroData[0], STREAM_BLOCK_SIZE, SUPPORTED_SAMPLING_RATE);
		}
	}
	// Let's just start playing the *empty* stream, during each tick time, the requested audio stream will
	// be added to the queue of the buffer list.
	alSourceQueueBuffers(s_streamSource, NUM_STREAM_BLOCKS, s_streamBuffers);
	alSourcePlay(s_streamSource);

	return true;
}

void Destory()
{
	StopAllSound();
	for (int i = 0; i < MAX_SOUND_EFFECT_MIX; ++i) {
		alDeleteSources(1, &s_soundSource[i].sourceId);
	}

	if (s_context)
		alcDestroyContext(s_context);

	if (s_device)
		alcCloseDevice(s_device);
}

SOUND_HANDLE OpenSound(const char* filename)
{
	std::map<std::string, int>::iterator it;
	it = s_bufferMap.find(filename);
	if (it == s_bufferMap.end()) {
		ALuint buffer;
		// Create the buffer and load its data
		alGenBuffers(1, &buffer);
		KWavReader wavReader;
		wavReader.OpenWavFile(filename);
		if (!wavReader.IsOk()) {
			alDeleteBuffers(1, &buffer);
			return NULL;
		}
		unsigned int sampleCnt = wavReader.GetSampleCnt();
		if (wavReader.IsStereo())
			alBufferData(buffer, AL_FORMAT_STEREO16, wavReader.GetSamplePtr(), sampleCnt * 2 * sizeof(short), wavReader.GetFreq());
		else
			alBufferData(buffer, AL_FORMAT_MONO16, wavReader.GetSamplePtr(), sampleCnt * sizeof(short), wavReader.GetFreq());

		SoundBuffer newBuffer;
		newBuffer.bufferId = buffer;
		newBuffer.sampleCnt = sampleCnt;
		s_soundBuffer.push_back(newBuffer);
		s_bufferMap[filename] = s_soundBuffer.size() - 1;
		return s_soundBuffer.size() - 1;
	}
	else {
		return it->second;
	}
}

void StopAllSound()
{
	for (int i = 0; i < MAX_SOUND_EFFECT_MIX; ++i) {
		alSourceStop(s_soundSource[i].sourceId);
		s_soundSource[i].currentBuffer = -1;
	}
}

void ClearAllSound()
{
	StopAllSound();
	for (int i = 0; i < MAX_SOUND_EFFECT_MIX; ++i) {
		alSourcei(s_soundSource[i].sourceId, AL_BUFFER, 0);
	}

	for (size_t i = 1; i < s_soundBuffer.size(); ++i) {
		alDeleteBuffers(1, &s_soundBuffer[i].bufferId);
	}
}


void PlaySound(SOUND_HANDLE hSound)
{
	int targetSourceIdx = -1;
	int i = 0;
	for (; i < MAX_SOUND_EFFECT_MIX; ++i) {
		ALint state;
		alGetSourcei(s_soundSource[i].sourceId, AL_SOURCE_STATE, &state);     
		if (s_soundSource[i].currentBuffer == -1 || state == AL_STOPPED) {
			// Found the available source slot
			targetSourceIdx = i;
			break;
		}		
	}

	if (i == MAX_SOUND_EFFECT_MIX) {
		// The sound sources is used up, I need to retire someone in order to
		// play the sound(since the new sound effect is always important than older ones).
		i = 0;
		float minCompleteRatio = 2.0f;
		for (; i < MAX_SOUND_EFFECT_MIX; ++i) {
			ALint state;
			alGetSourcei(s_soundSource[i].sourceId, AL_SOURCE_STATE, &state);  
			if (state == AL_PLAYING) {
				ALint sampleOffset;
				alGetSourcei(s_soundSource[i].sourceId, AL_SAMPLE_OFFSET, &sampleOffset);
				float completeRatio = (float)sampleOffset / (float)s_soundBuffer[s_soundSource[i].currentBuffer].sampleCnt;
				if (minCompleteRatio > completeRatio) {
					minCompleteRatio = completeRatio;
					targetSourceIdx = i;
				}
			}
		}

	}

	if (targetSourceIdx != -1) {
		alSourcei(s_soundSource[targetSourceIdx].sourceId, AL_BUFFER, s_soundBuffer[hSound].bufferId);
		alSourcePlay(s_soundSource[targetSourceIdx].sourceId);
	}
}

void TimeElapse(unsigned int ms)
{
	// Check if the stream buffer need to be filled
	ALint val;
	alGetSourcei(s_streamSource, AL_BUFFERS_PROCESSED, &val);
	if(val <= 0)
		return; // All the buffers are still in the "to-be-played" lists

	// Some buffer has been retired, let's fill it with new data
	ALuint buffer;
	
	while(val--)
	{
		alSourceUnqueueBuffers(s_streamSource, 1, &buffer);
		memset(&s_streamDecodingBuffer[0], 0, STREAM_BLOCK_SIZE);
		for (int i = 0; i < MAX_SOUND_STREAM_MIX; ++i) {
			if (s_mp3Streams[i]) {
				int requestedSampCnt = STREAM_BLOCK_SIZE / sizeof(short) / 2;
				s_mp3Streams[i]->MixWithStreamBuffer((short*)&s_streamDecodingBuffer[0], requestedSampCnt);
				if (s_mp3Streams[i]->IsEOF()) {
					// In this case the stream 
					s_mp3Streams[i]->Reset();
				}
			}
		}

		alBufferData(buffer, AL_FORMAT_STEREO16, &s_streamDecodingBuffer[0], STREAM_BLOCK_SIZE, SUPPORTED_SAMPLING_RATE);
		alSourceQueueBuffers(s_streamSource, 1, &buffer);
		if(alGetError() != AL_NO_ERROR)
			break;
	}

	alGetSourcei(s_streamSource, AL_SOURCE_STATE, &val);
	if(val != AL_PLAYING)
		alSourcePlay(s_streamSource);
}


int PlayStream(const char* filename)
{
	for (int i = 0; i < MAX_SOUND_STREAM_MIX; ++i) {
		if (s_mp3Streams[i] == NULL) {
			s_mp3Streams[i] = new KMp3Decoder;
			if (s_mp3Streams[i]->OpenFile(filename))
				return i;
			else {
				delete s_mp3Streams[i];
				s_mp3Streams[i] = NULL;
				return -1;
			}
		}
	}

	// All the stream slots are used up(thus cannot play any more streams)
	return -1;
}

void StopStream(int streamIdx)
{
	if (streamIdx >= 0 && streamIdx < MAX_SOUND_STREAM_MIX) {
		if (s_mp3Streams[streamIdx]) {
			delete s_mp3Streams[streamIdx];
			s_mp3Streams[streamIdx] = NULL;
		}
	}
}

} // namespace KAudio